package org.byacko.simplemock;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * モックを生成するクラス
 * @author koumiya
 *
 * @param <T>
 */
public class MockCreater<T> {

	private Class<T> type;
	private MethodHooks methodHooks = new MethodHooks();
	private Object assertObject;
	private T delegateObject;
	private boolean delegateAll = false;
	
	protected MockCreater(Class<T> type) {
		this.type = type;
	}

	/**
	 * モックオブジェクトを生成する
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T create() {
		return (T)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new InvocationHandler() {
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				final boolean findNoArg = !delegateAll;
				final MethodHook mh = methodHooks.lookup(method, findNoArg);
				if(mh != null) {
					if(mh instanceof ExpectMethodHook && ((ExpectMethodHook)mh).isAssertable()) {
						((ExpectMethodHook)mh).invokeAssertion(args);
					}
					return mh.process(method, args);
				}
				else if(delegateAll) {
					return method.invoke(delegateObject, args);
				} else {
					return null;
				}
			}
		});
	}

	/**
	 * メソッド名を指定してスタブを作成する
	 * @param methodName
	 * @return
	 */
	public StubBinder<T> stub(String methodName) {
		return new StubBinder<T>(this, methodName);
	}
	
	/**
	 * メソッド名を指定してモックを作成する
	 * @param methodName
	 * @return
	 */
	public ExpectBinder<T> expect(String methodName) {
		return new ExpectBinder<T>(this, methodName);
	}
	
	public MockCreater<T> setAssertObject(Object object) {
		this.assertObject = object;
		return this;
	}
	
	public MockCreater<T> setDelegateObject(T delegateObject) {
		this.delegateObject = delegateObject;
		return this;
	}
	
	public MockCreater<T> delegateAll() {
		if(delegateObject == null) {
			throw new IllegalStateException("delegateAllを使用するには、delegateObjectを設定してください。");
		}
		this.delegateAll  = true;
		return this;
	}

	protected void bind(MethodHook mh) {
		methodHooks.bind(mh);
	}

	protected Object getAssertObject() {
		return this.assertObject;
	}

	protected T getDelegateObject() {
		return this.delegateObject;
	}
}
